package com.yeziji.config.support.handler;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.yeziji.annotation.AutowiredRolePermission;
import com.yeziji.annotation.EnableAutowiredPermission;
import com.yeziji.common.CommonSymbol;
import com.yeziji.common.business.system.constant.enums.SystemRoleTypeEnum;
import com.yeziji.common.business.system.dto.SystemRolePermissionDTO;
import com.yeziji.common.business.system.entity.SystemPermissionEntity;
import com.yeziji.common.business.system.service.SystemPermissionService;
import com.yeziji.common.business.system.service.SystemRolePermissionRelationService;
import com.yeziji.utils.NanoIdUtils;
import com.yeziji.utils.ThreadPoolUtils;
import com.yeziji.utils.expansion.Lists2;
import com.yeziji.utils.expansion.Spring2;
import com.yeziji.utils.expansion.Str2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 自动装配权限处理器
 *
 * @author hwy
 * @since 2024/04/30 16:02
 **/
@Slf4j
@Component
public class AutowiredPermissionHandler implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware, ApplicationListener<ContextRefreshedEvent> {
    /**
     * 环境
     */
    private Environment environment;
    /**
     * 资源加载器
     */
    private ResourceLoader resourceLoader;
    /**
     * 自动装配的放行地址
     */
    private static final ThreadLocal<Map<String, SystemPermissionEntity>> AUTOWIRED_ROLE_PERMISSION_MAP = new ThreadLocal<>();

    @Resource
    private SystemPermissionService systemPermissionService;
    @Resource
    private SystemRolePermissionRelationService systemRolePermissionRelationService;

    @Override
    public void setEnvironment(@NonNull Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(@NonNull ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) {
        // 扫描指定的 loader
        ClassPathScanningCandidateComponentProvider scanner = Spring2.getScanner(this.environment, this.resourceLoader);

        // 设置扫描过滤条件
        scanner.addIncludeFilter(new AnnotationTypeFilter(Controller.class));
        scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class));
        scanner.addIncludeFilter(new AnnotationTypeFilter(AutowiredRolePermission.class));

        // 获取扫描并遍历 basePackages
        Map<String, SystemPermissionEntity> rolePathPermissionMap = new LinkedHashMap<>();
        Set<String> basePackages = Spring2.getBasePackages(EnableAutowiredPermission.class, importingClassMetadata);
        for (String basePackage : basePackages) {
            // 通过 scanner 获取 basePackage 下的候选类(有标 @RestController/@Controller 注解的类)
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            // 遍历每一个候选类，如果符合条件就把他们记录下来
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    // verify annotated class is an interface
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();

                    // 优先执行 annotatedMethods 的权限; 如果不存在则走类的权限
                    Set<MethodMetadata> annotatedMethods = annotationMetadata.getAnnotatedMethods(AutowiredRolePermission.class.getCanonicalName());
                    if (Lists2.isNotEmpty(annotatedMethods)) {
                        for (MethodMetadata annotatedMethod : annotatedMethods) {
                            rolePathPermissionMap.putAll(this.getAnnotatedTypeMetadataPermissionStrList(annotationMetadata, annotatedMethod, true));
                        }
                    } else {
                        rolePathPermissionMap.putAll(this.getAnnotatedTypeMetadataPermissionStrList(null, annotationMetadata, false));
                    }
                }
            }
        }

        // set thread local
        if (!rolePathPermissionMap.isEmpty()) {
            AUTOWIRED_ROLE_PERMISSION_MAP.set(rolePathPermissionMap);
        }
    }

    @Override
    public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
        log.info("autowired permission refresh event start !!!");
        try {
            // [USER&ADMIN$/user/login: entity1, USER&ADMIN$/user/register: entity2, ...]
            Map<String, SystemPermissionEntity> autowiredRolePermissionMap = AUTOWIRED_ROLE_PERMISSION_MAP.get();
            if (MapUtil.isEmpty(autowiredRolePermissionMap)) {
                return;
            }

            Map<String, Set<SystemPermissionEntity>> rolePermissionMap = new HashMap<>();
            Set<Map.Entry<String, SystemPermissionEntity>> entries = autowiredRolePermissionMap.entrySet();
            for (Map.Entry<String, SystemPermissionEntity> entry : entries) {
                String rolePath = entry.getKey();
                SystemPermissionEntity permission = entry.getValue();
                // $ 分割出: role + path 的信息
                String[] splitter = rolePath.split("\\$");

                // 先获取 path, 如果 path 为空就不处理; 反之赋值到当前 permission 的 params 当中
                String path = splitter[1];
                if (StrUtil.isBlank(path)) {
                    continue;
                }
                permission.setParams(path);

                // 遍历角色, 为每个角色赋值对应的权限
                List<String> roleNames = Lists2.splitter(CommonSymbol.AND_SYMBOL, splitter[0]);
                for (String roleName : roleNames) {
                    rolePermissionMap.computeIfAbsent(roleName, k -> new HashSet<>()).add(permission);
                }
            }

            // 异步存储
            ThreadPoolUtils.customExecute(() -> {
                if (this.systemPermissionService != null && !rolePermissionMap.isEmpty()) {
                    log.info("异步保存放行地址: {}", rolePermissionMap);
                    // add role permission
                    for (Map.Entry<String, Set<SystemPermissionEntity>> rolePermissionEntry : rolePermissionMap.entrySet()) {
                        String roleName = rolePermissionEntry.getKey();
                        Set<SystemPermissionEntity> permissions = rolePermissionEntry.getValue();
                        if (Lists2.isEmpty(permissions)) {
                            continue;
                        }
                        // 先保存角色的权限
                        systemPermissionService.saveBatch(permissions);
                        // 再为角色追加权限
                        for (SystemPermissionEntity permission : permissions) {
                            systemRolePermissionRelationService.addRolePermission(
                                    SystemRolePermissionDTO.builder()
                                            .roleName(roleName)
                                            .permissionId(permission.getId())
                                            .build()
                            );
                        }
                    }
                }
            });
        } finally {
            AUTOWIRED_ROLE_PERMISSION_MAP.remove();
        }
    }

    /**
     * 获取 AnnotatedTypeMetadata 的权限信息
     *
     * @param annotationMetadata 权限元对象
     * @param isEndPath          是否直接捕获当前地址
     * @return {@link Map} {key: role$path, value: autowired role permission}
     */
    private Map<String, SystemPermissionEntity> getAnnotatedTypeMetadataPermissionStrList(AnnotationMetadata parentMetadata, AnnotatedTypeMetadata annotationMetadata, boolean isEndPath) {
        Map<String, SystemPermissionEntity> result = new LinkedHashMap<>();
        // 获取自动绑定的角色权限的注解
        Map<String, Object> permissionTypeAttributes =
                annotationMetadata.getAnnotationAttributes(AutowiredRolePermission.class.getCanonicalName());
        // 获取 @RequestMapping 注解, 如果为空就返回 null，用于略过
        Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(RequestMapping.class.getCanonicalName());
        if (attributes == null || permissionTypeAttributes == null) {
            return result;
        }

        // 获取父类前缀
        List<String> parentPaths = new ArrayList<>();
        if (parentMetadata != null && isEndPath) {
            Map<String, Object> parentAttributes = parentMetadata.getAnnotationAttributes(RequestMapping.class.getCanonicalName());
            if (parentAttributes == null) {
                return result;
            }
            String[] paths = (String[]) parentAttributes.get("path");
            for (String path : paths) {
                // 若不是 / 直接追加 /
                if (!path.endsWith(CommonSymbol.LEFT_DIAGONAL_BAR)) {
                    path += "/";
                }
                parentPaths.add(path);
            }
        }

        // 构造一个初始化的权限实体, 并通过反射将注解值赋值到实体类中
        String key = Str2.isBlankElse(String.valueOf(permissionTypeAttributes.get("key")), NanoIdUtils.randomNotSymbolNaoId(8));
        SystemPermissionEntity systemPermissionEntity = new SystemPermissionEntity(key);
        Map<String, Field> fieldMap = ReflectUtil.getFieldMap(SystemPermissionEntity.class);
        for (Map.Entry<String, Object> entry : permissionTypeAttributes.entrySet()) {
            String fieldName = entry.getKey();
            Object fieldValue = entry.getValue();
            Field field = fieldMap.get(fieldName);
            if (field != null && fieldValue != null) {
                ReflectUtil.setFieldValue(systemPermissionEntity, fieldName, fieldValue);
            }
        }

        // 记录角色信息
        SystemRoleTypeEnum[] systemRoleTypeEnums = (SystemRoleTypeEnum[]) permissionTypeAttributes.get("roleTypes");
        StringJoiner systemRoleTypeEnumStrJoiner = new StringJoiner(CommonSymbol.AND_SYMBOL);
        for (SystemRoleTypeEnum systemRoleTypeEnum : systemRoleTypeEnums) {
            systemRoleTypeEnumStrJoiner.add(systemRoleTypeEnum.getDesc());
        }

        // 获取 requestMapping, 这里一般只有一个
        String[] paths = (String[]) attributes.get("path");
        for (String path : paths) {
            // USER&ADMIN$/user/login
            String lastPath = systemRoleTypeEnumStrJoiner + CommonSymbol.DOLLAR;
            // 存在父级的时候以父级为主进行拼接
            if (Lists2.isNotEmpty(parentPaths) && isEndPath) {
                // remove start /
                if (path.startsWith("/")) {
                    path = StrUtil.replaceFirst(path, "/", "");
                }
                // parent + path = /admin/save
                for (String parentPath : parentPaths) {
                    result.put(lastPath + parentPath + path, systemPermissionEntity);
                    systemPermissionEntity.setParams(lastPath + parentPath + path);
                }
            } else {
                // 若不是 / 结尾直接追加 /**
                if (!path.endsWith(CommonSymbol.LEFT_DIAGONAL_BAR)) {
                    lastPath += isEndPath ? path : path + "/**";
                } else {
                    lastPath += isEndPath ? path : path + "**";
                }
                result.put(lastPath, systemPermissionEntity);
            }
        }
        return result;
    }
}